package com.youlin.service.impl;

import com.aliyun.oss.common.utils.IOUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlin.dto.*;
import com.youlin.dto.req.EditAMReqDto;
import com.youlin.dto.req.FindAllAmReqDto;
import com.youlin.dto.req.SetPosterNameReqDto;
import com.youlin.dto.req.UpdAMStatusReqDto;
import com.youlin.dto.res.*;
import com.youlin.entities.*;
import com.youlin.mapper.BygAppointmentMapper;
import com.youlin.security.SecurityUser;
import com.youlin.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlin.untils.*;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.assertj.core.util.Lists;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import javax.imageio.ImageIO;
import javax.jms.JMSException;
import javax.jms.TextMessage;
import javax.jms.Topic;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

@Service
public class BygAppointmentServiceImpl extends ServiceImpl<BygAppointmentMapper, BygAppointment> implements BygAppointmentService {

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private BygUndertakerService bygUndertakerService;
    @Autowired
    private BygAdminService bygAdminService;
    @Autowired
    private BygDeadManService bygDeadManService;
    @Autowired
    private BygFarewellHallService bygFarewellHallService;
    @Autowired
    private BygRelationService bygRelationService;
    @Autowired
    private BygAppointmentService bygAppointmentService;
    @Autowired
    private ConcurrentHashMap<String, SoftReference<Boolean>> inlCMap;
    @Autowired
    private ConcurrentHashMap<String, SoftReference<Integer>> amStatusCMap;
    @Autowired
    private JmsMessagingTemplate jmsMessagingTemplate;
    @Autowired
    private Topic dmTopic;
    @Autowired
    private Topic amStatusTopic;
    @Autowired
    private Topic inlTopic;
    @Autowired
    private BygAppInfoService bygAppInfoService;
    @Autowired
    private WxSecCheckUtil wxSecCheckUtil;
    @Autowired
    private MyTxUtil myTxUtil;
    @Autowired
    private MyCacheUtil myCacheUtil;
    @Autowired
    private BygRoleService bygRoleService;
    @Autowired
    private SessionRegistry sessionRegistry;
    @Autowired
    private BygBgTemplateService bygBgTemplateService;
    @Autowired
    private OSSUtil ossUtil;

    @Override
    public PageDataResDto<List<AppointmentResDto>> findAllAppointments(
            BygAdmin currentAdmin, FindAllAmReqDto data, PageInfo pageInfo) {

        List<AppointmentResDto> finalAppointments = Lists.newArrayList();
        QueryWrapper<BygAppointment> queryWrapper = new QueryWrapper<>();

        // 异步获取 告别厅筛选条件
        CompletableFuture<List<BygFarewellHall>> fhFuture = CompletableFuture.supplyAsync(() -> {
            if (data.getFhId() != null) {
                return Arrays.asList(bygFarewellHallService.getById(data.getFhId()));
            } else if (data.getAppId() != null) {
                List<BygFarewellHall> farewellHalls = bygFarewellHallService
                        .list(Wrappers.lambdaQuery(BygFarewellHall.class)
                                .eq(BygFarewellHall::getUnitId, data.getAppId()));
                if (CollectionUtils.isEmpty(farewellHalls))
                    farewellHalls.add(new BygFarewellHall().setId(Integer.MAX_VALUE));
                return farewellHalls;
            } else return null;
        });

        // 异步获取 操作者或审核中筛选条件(审核者仅可见下辖操作者创建的告别会信息)
        CompletableFuture<List<BygAdmin>> roleFuture = CompletableFuture.supplyAsync(() -> {
            // 查询用户为操作者
            if (currentAdmin.getRoleId() == MyEnum.ROLE_OPERATOR_ID.getValue())
                queryWrapper.eq("operator_id", currentAdmin.getId())
                        .lt("status", MyEnum.AM_PRE_REVIEW.getValue());

            // 查询用户为审核者，查询其下辖的操作者信息并返回
            if (currentAdmin.getRoleId() == MyEnum.ROLE_REVIEWER_ID.getValue()) {
                LambdaQueryWrapper<BygAdmin> adminWrapper = Wrappers
                        .lambdaQuery(BygAdmin.class).eq(BygAdmin::getPid, currentAdmin.getId());
                List<BygAdmin> operators = bygAdminService.list(adminWrapper);
                List<Integer> operatorIds = operators.parallelStream()
                        .collect(Collectors.mapping(BygAdmin::getId, Collectors.toList()));

                queryWrapper.nested(qw -> {
                    qw.eq("reviewer_id", currentAdmin.getId());
                    if (CollectionUtils.isNotEmpty(operatorIds))
                        qw.or().eq("status", MyEnum.AM_PRE_REVIEW.getValue())
                                .in("operator_id", operatorIds);
                });
                return operators;
            }

            return null;
        });

        // 异步获取 承办人筛选条件(承办人姓名或承办人电话)
        CompletableFuture<List<BygUndertaker>> takerFuture = CompletableFuture.supplyAsync(() -> {
            if (StringUtils.isNotBlank(data.getTakerName()) || StringUtils.isNotBlank(data.getTakerTel())) {
                LambdaQueryWrapper<BygUndertaker> wrapper = Wrappers.lambdaQuery(BygUndertaker.class);
                if (StringUtils.isNotBlank(data.getTakerTel()))
                    wrapper.eq(BygUndertaker::getTel, data.getTakerTel());
                if (StringUtils.isNotBlank(data.getTakerName()))
                    wrapper.eq(BygUndertaker::getName, data.getTakerName());

                List<BygUndertaker> undertakers = bygUndertakerService.list(wrapper);
                List<Integer> amIds = undertakers.parallelStream()
                        .map(BygUndertaker::getAmId).collect(Collectors.toList());
                if (amIds.size() > 0) queryWrapper.in("id", amIds);
                else queryWrapper.in("id", Arrays.asList(-1));
                return undertakers;
            } else return null;
        });

        // 异步获取 逝者信息筛选条件
        CompletableFuture<List<BygDeadMan>> dmFuture = CompletableFuture.supplyAsync(() -> {
            if (StringUtils.isNotBlank(data.getDeadName())) {
                LambdaQueryWrapper<BygDeadMan> wrapper = Wrappers.lambdaQuery(BygDeadMan.class)
                        .eq(BygDeadMan::getName, data.getDeadName());
                List<BygDeadMan> deadMams = bygDeadManService.list(wrapper);
                List<Integer> dmIds = deadMams.parallelStream()
                        .map(BygDeadMan::getId).collect(Collectors.toList());
                if (dmIds.size() > 0) queryWrapper.in("dm_id", dmIds);
                else queryWrapper.in("dm_id", Arrays.asList(-1));
                return deadMams;
            }
            return null;
        });

        // 异步获取 告别会信息数据
        CompletableFuture<Void> amFuture = CompletableFuture
                .allOf(roleFuture, takerFuture, dmFuture).thenRunAsync(() -> {
                    if (data.getStatus() != null) queryWrapper.eq("status", data.getStatus());
                    if (data.getUseDate() != null) queryWrapper.eq("use_date", data.getUseDate());
                    if (data.getOperatorId() != null) queryWrapper.eq("operator_id", data.getOperatorId());
                    if (data.getReviewerId() != null) queryWrapper.eq("reviewer_id", data.getReviewerId());
                    List<BygFarewellHall> farewellHalls = fhFuture.join();
                    if (CollectionUtils.isNotEmpty(farewellHalls)) {
                        List<Integer> fhIds = farewellHalls.parallelStream()
                                .collect(Collectors.mapping(BygFarewellHall::getId, Collectors.toList()));
                        queryWrapper.in("fh_id", fhIds);
                    }
                    if (data.getCreateDate() != null) queryWrapper
                            .ge("create_time", LocalDateTime.of(data.getCreateDate(), LocalTime.MIN))
                            .le("create_time", LocalDateTime.of(data.getCreateDate(), LocalTime.MAX));

                    IPage<BygAppointment> page = page(new Page<>(pageInfo.getPage(),
                            pageInfo.getSize()), queryWrapper.orderByDesc("create_time"));
                    pageInfo.setTotal(page.getTotal());
                    finalAppointments.addAll(page.getRecords().parallelStream().map(a ->
                            BaseService.copyProperties(a, AppointmentResDto.class)).collect(Collectors.toList()));
                }, threadPoolExecutor);

        // 异步操作 组合响应数据逝者信息
        CompletableFuture<Void> setDeadManFuture = amFuture.thenRunAsync(() -> {
            if (finalAppointments.size() > 0) {
                List<BygDeadMan> deadMams = dmFuture.join();
                if (CollectionUtils.isEmpty(deadMams)) BaseService.findAndDealWithOneToOne(finalAppointments,
                        AppointmentResDto::getDmId, AppointmentResDto.class, bygDeadManService, BygDeadMan::getId);
                else BaseService.dealWithOneToOne(finalAppointments, AppointmentResDto::getDmId,
                        AppointmentResDto.class, deadMams, BygDeadMan::getId);
            }
        }, threadPoolExecutor);

        // 异步操作 组合响应数据告别厅信息
        CompletableFuture<Void> setFhFuture = amFuture.thenRunAsync(() -> {
            if (finalAppointments.size() > 0) {
                List<BygFarewellHall> farewellHalls = fhFuture.join();
                if (CollectionUtils.isNotEmpty(farewellHalls)) BaseService.dealWithOneToOne(finalAppointments,
                        AppointmentResDto::getFhId, AppointmentResDto.class, farewellHalls, BygFarewellHall::getId);
                else BaseService.findAndDealWithOneToOne(finalAppointments, AppointmentResDto::getFhId,
                        AppointmentResDto.class, bygFarewellHallService, BygFarewellHall::getId);
            }
        }, threadPoolExecutor);

        // 异步操作 组合响应数据承办人信息
        CompletableFuture<Void> setUndertakerFuture = amFuture.thenRunAsync(() -> {
            if (finalAppointments.size() > 0) {
                List<Integer> amIds = finalAppointments.parallelStream()
                        .map(AppointmentResDto::getId).collect(Collectors.toList());
                List<BygUndertaker> undertakers = bygUndertakerService
                        .list(Wrappers.lambdaQuery(BygUndertaker.class).in(BygUndertaker::getAmId, amIds));
                List<TakerResDto> takerResDtos = BaseService.findAndDealWithOneToOne(undertakers,
                        BygUndertaker::getRelationId, TakerResDto.class, bygRelationService, BygRelation::getId);
                BaseService.dealWithOneToMany(finalAppointments, AppointmentResDto::getId,
                        AppointmentResDto.class, takerResDtos, TakerResDto::getAmId);
            }
        }, threadPoolExecutor);

        // 异步操作 组合响应数据操作者信息
        CompletableFuture<Void> setOperatorFuture = amFuture.thenRunAsync(() -> {
            if (finalAppointments.size() > 0)
                if (CollectionUtils.isNotEmpty(roleFuture.join())) BaseService.dealWithOneToOne(finalAppointments,
                        AppointmentResDto::getOperatorId, AppointmentResDto.class, roleFuture.join(), BygAdmin::getId);
                else BaseService.findAndDealWithOneToOne(finalAppointments, AppointmentResDto::getOperatorId,
                        AppointmentResDto.class, bygAdminService, BygAdmin::getId);
        }, threadPoolExecutor);

        // 异步操作 组合响应数据审核者信息
        CompletableFuture<Void> setReviewerFuture = amFuture.thenRunAsync(() -> {
            if (finalAppointments.size() > 0) BaseService.findAndDealWithOneToOne(finalAppointments,
                    AppointmentResDto::getReviewerId, AppointmentResDto.class, bygAdminService, BygAdmin::getId);
        }, threadPoolExecutor);

        // 异步操作 组合响应数据汇总返回
        return CompletableFuture.allOf(setDeadManFuture, setFhFuture, setUndertakerFuture, setOperatorFuture, setReviewerFuture)
                .thenApplyAsync(r -> new PageDataResDto<List<AppointmentResDto>>(Status.SUCCESS)
                        .setData(finalAppointments).setPageInfo(pageInfo)).join();
    }

    @Override
    @Transactional
    public BaseResDto<Status, String> delAppointment(Integer amId) {
        BygAppointment appointment = getById(amId);
        if (appointment == null) return new BaseResDto<>(Status.PARAMETERERROR);
        if (appointment.getStatus() > MyEnum.AM_CREATE.getValue())
            return new BaseResDto<>(Status.ONLYDELCREATE);
        if (!removeById(amId)) return new BaseResDto<>(Status.ERROR);

        CompletableFuture.runAsync(() -> {
            BygFarewellHall farewellHall = bygFarewellHallService.getById(appointment.getFhId());
            synchronized (this) {
                BygAppInfo appInfo = bygAppInfoService.getById(farewellHall.getUnitId());
                bygAppInfoService.updateById(appInfo.setAmCount(appInfo.getAmCount() - 1));
            }
        });

        return new BaseResDto<>(Status.SUCCESS);
    }

    @Override
    public BaseResDto<Status, String> editAppointment(BygAdmin currentAdmin, EditAMReqDto data) {
        // 校验逝者生日和忌日是否合法
        LocalDate dBirthday = data.getDeadBirthday();
        LocalDate dDeadday = data.getDeadDeadday();
        if (dBirthday.isAfter(dDeadday) || dDeadday.isAfter(LocalDate.now()))
            return new BaseResDto<>(Status.DDATEERROR);
        LocalTime startTime = data.getStartTime();
        LocalTime endTime = data.getEndTime();
        LocalDate useDate = data.getUseDate();
        if (data.getAmId() != null) {
            BygAppointment appointment = getById(data.getAmId());
            if (!useDate.isEqual(appointment.getUseDate()) || !startTime.equals(appointment.getStartTime())
                    || !endTime.equals(appointment.getEndTime())) {
                if (startTime.isAfter(endTime) || useDate.isBefore(LocalDate.now()))
                    return new BaseResDto<>(Status.USETIMEERROR);
                if (useDate.isEqual(LocalDate.now()) && startTime.isBefore(LocalTime.now()))
                    return new BaseResDto<>(Status.USETIMEERROR);
            }
        } else {
            if (startTime.isAfter(endTime) || useDate.isBefore(LocalDate.now()))
                return new BaseResDto<>(Status.USETIMEERROR);
            if (useDate.isEqual(LocalDate.now()) && startTime.isBefore(LocalTime.now()))
                return new BaseResDto<>(Status.USETIMEERROR);
        }

        // 校验告别厅使用日期时间是否存在时间重合冲突
        QueryWrapper<BygAppointment> queryWrapper = new QueryWrapper<BygAppointment>()
                .eq("fh_id", data.getFhId()).eq("use_date", useDate);
        if (data.getAmId() != null) queryWrapper.ne("id", data.getAmId());
        List<BygAppointment> appointments = list(queryWrapper);
        if (MyTimeUtil.checkAmUseTime(appointments, startTime, endTime))
            return new BaseResDto<>(Status.USETIMECONFLICT);

        try {
            // 修改/新建 告别会数据
            if (data.getAmId() != null) return updAppointment(data);
            else return bygAppointmentService.addAppointment(currentAdmin, data);
            // bygAppointmentService.addAppointment(currentAdmin, data) 非事务方法调用事务方法，使AOP动态代理生效
        } catch (TelErrorExpectation e) {
            return new BaseResDto<>(Status.TELEDUPLICATE);
        }
    }

    // 新建告别会数据
    @Override
    @Transactional
    public BaseResDto<Status, String> addAppointment(BygAdmin currentAdmin, EditAMReqDto data) {

        // 新建保存逝者信息
        BygDeadMan deadMan = new BygDeadMan().setGender(data.getDeadGender())
                .setName(data.getDeadName()).setUpdateTime(LocalDateTime.now())
                .setBirthday(data.getDeadBirthday()).setDeadday(data.getDeadDeadday());
        if (! bygDeadManService.save(deadMan)) return new BaseResDto<>(Status.ERROR);

        // 新建保存告别会信息
        BygAppointment appointment = new BygAppointment()
                .setDmId(deadMan.getId()).setUuId(Md5Util.getUUID())
                .setFhId(data.getFhId()).setOperatorId(currentAdmin.getId())
                .setStartTime(data.getStartTime()).setEndTime(data.getEndTime())
                .setUseDate(data.getUseDate()).setCreateTime(LocalDateTime.now());
        if (! save(appointment)) return new BaseResDto<>(Status.ERROR);

        // 承办人关系表保存告别会ID
        BaseResDto<Status, String> editTakerBase = bygUndertakerService
                .editUnderTaker(appointment.getId(), deadMan.getId(), data.getTakers());
        if (editTakerBase.equals(new BaseResDto<>(Status.TELEDUPLICATE))) throw new TelErrorExpectation();
        if (editTakerBase.equals(new BaseResDto<>(Status.ERROR))) return editTakerBase;

        CompletableFuture.runAsync(() -> {
            BygFarewellHall farewellHall = bygFarewellHallService.getById(data.getFhId());
            synchronized (this) {
                // 修改告别会所属单位的告别会数量
                BygAppInfo appInfo = bygAppInfoService.getById(farewellHall.getUnitId());
                bygAppInfoService.updateById(appInfo.setAmCount(appInfo.getAmCount() + 1));
            }
        });

        return new BaseResDto<>(Status.SUCCESS);
    }

    // 修改告别会数据
    public BaseResDto<Status, String> updAppointment(EditAMReqDto data) {
        BygAppointment am = getById(data.getAmId());
        if (am != null) {
            CyclicBarrier cyclicBarrier = new CyclicBarrier(3); // 创建线程屏障
            AtomicBoolean rollbackFlag = new AtomicBoolean(false); // 创建事务回滚开关

            // 异步编辑 承办人相关数据
            CompletableFuture<Void> future2 = CompletableFuture.runAsync(myTxUtil.txRunnable(cyclicBarrier, rollbackFlag, () -> {

                BaseResDto<Status, String> editTakerBase = bygUndertakerService
                        .editUnderTaker(am.getId(), am.getDmId(), data.getTakers());
                if (editTakerBase.equals(new BaseResDto<>(Status.TELEDUPLICATE))) throw new TelErrorExpectation();

                return editTakerBase.equals(new BaseResDto<>(Status.SUCCESS));
            }), threadPoolExecutor);

            // 异步编辑 逝者相关数据
            CompletableFuture<Void> future3 = CompletableFuture.runAsync(myTxUtil.txRunnable(cyclicBarrier, rollbackFlag, () -> {
                BygDeadMan deadMan = new BygDeadMan().setName(data.getDeadName()).setBirthday(data.getDeadBirthday())
                        .setId(am.getDmId()).setDeadday(data.getDeadDeadday()).setGender(data.getDeadGender());
                return bygDeadManService.updateById(deadMan);
            }), threadPoolExecutor);

            // 异步编辑 告别会相关数据
            CompletableFuture<Void> future4 = CompletableFuture.runAsync(myTxUtil.txRunnable(cyclicBarrier, rollbackFlag, () -> {
                LambdaUpdateWrapper<BygAppointment> wrapper = Wrappers.lambdaUpdate(BygAppointment.class)
                        .eq(BygAppointment::getId, am.getId()).set(BygAppointment::getFhId, data.getFhId())
                        .set(BygAppointment::getUseDate, data.getUseDate()).set(BygAppointment::getStartTime, data.getStartTime())
                        .set(BygAppointment::getEndTime, data.getEndTime()).set(BygAppointment::getUpdateTime, LocalDateTime.now());

                BygDeadMan deadMan = bygDeadManService.getById(am.getDmId());
                if (!deadMan.getName().equals(data.getDeadName()) || !deadMan.getBirthday().equals(data.getDeadBirthday())
                        || !deadMan.getDeadday().equals(data.getDeadDeadday()))
                    wrapper.set(BygAppointment::getPosterUrl, null);

                return bygAppointmentService.update(wrapper);
            }), threadPoolExecutor);

            // 响应请求并清除本地缓存和Redis缓存
            try {
                return CompletableFuture.allOf(future2, future3, future4).thenApplyAsync(x -> {
                    if (rollbackFlag.get()) return new BaseResDto<>(Status.ERROR);
                    jmsMessagingTemplate.convertAndSend(dmTopic, MyEnum.AMUUID_DM.getMessage() + am.getUuId());
                    return new BaseResDto<>(Status.SUCCESS);
                }).join();
            } catch (Exception e) {
                e.printStackTrace();
                if (e.getCause() instanceof TelErrorExpectation)
                    return new BaseResDto<>(Status.TELEDUPLICATE);
                return new BaseResDto<>(Status.ERROR);
            }
        }
        return new BaseResDto<>(Status.ERROR);
    }

    @Override
    public BygAppointment findAppointmentByUuId(String amUuId) {
        LambdaQueryWrapper<BygAppointment> lambdaQueryWrapper = Wrappers
                .lambdaQuery(BygAppointment.class).eq(BygAppointment::getUuId, amUuId);
        return getOne(lambdaQueryWrapper);
    }

    @Override
    @Transactional
    public BaseResDto<Status, String> updAMStatus(UpdAMStatusReqDto data) {
        // 获取告别会信息数据
        BygAppointment appointment = getById(data.getAmId());
        if (appointment == null) return new BaseResDto<>(Status.PARAMETERERROR);

        // 修改告别会状态只能逐级递增
        Integer status = data.getStatus();
        if (status < appointment.getStatus() || status - appointment.getStatus() > 1)
            return new BaseResDto<>(Status.UPDAMSTATUSERROR);

        // 修改告别会状态为 确定预约
        if (status == MyEnum.AM_OPERATE.getValue()) {
            BygDeadMan deadMan = bygDeadManService.getById(appointment.getDmId());
            if (MyBeanUtil.isParamError(deadMan)) return new BaseResDto<>(Status.AMOPERATEERROR);
            if ((StringUtils.isBlank(deadMan.getLifeTitle1()) || StringUtils.isBlank(deadMan.getLifeStory1())) &&
                    (StringUtils.isBlank(deadMan.getLifeTitle2()) || StringUtils.isBlank(deadMan.getLifeStory2())))
                return new BaseResDto<>(Status.AMOPERATEERROR);

            String wxAcodeUrl = wxSecCheckUtil.getWxAcode(appointment.getUuId(), 0);
            if (StringUtils.isBlank(wxAcodeUrl)) return new BaseResDto<>(Status.ERROR);
            appointment.setWxAcodeUrl(wxAcodeUrl).setOperateTime(LocalDateTime.now());
        }

        // 修改告别会状态为 仪式完成-待审核
        if (status == MyEnum.AM_PRE_REVIEW.getValue()) {
//            if (LocalDate.now().isBefore(appointment.getUseDate()))
//                return new BaseResDto<>(Status.AMUNDONE);
//            if (LocalDate.now().isEqual(appointment.getUseDate())
//                    && LocalTime.now().isBefore(appointment.getEndTime()))
//                return new BaseResDto<>(Status.AMUNDONE);
            appointment.setPreReviewTime(LocalDateTime.now());
        }

        // 修改告别会状态为 仪式完成-审核中
        if (status == MyEnum.AM_REVIEWING.getValue()) {
            BygAdmin currentAdmin = AdminUtil.getCurrentAdmin();
            appointment.setReviewerId(currentAdmin.getId()).setReviewingTime(LocalDateTime.now());
        }

        // 修改告别会状态为 仪式完成-已审核
        if (status == MyEnum.AM_POST_REVIEW.getValue()) {
//            LambdaQueryWrapper<BygSignIn> wrapper = Wrappers.lambdaQuery(BygSignIn.class)
//                    .eq(BygSignIn::getAmId, appointment.getId())
//                    .and(w -> w.isNotNull(BygSignIn::getNameHit).or().isNotNull(BygSignIn::getContentHit));
//            if (bygSignInService.count(wrapper) > 0) return new BaseResDto<>(Status.HASUNREVIEWED);
            appointment.setPostReviewTime(LocalDateTime.now());
        }
        appointment.setStatus(status).setUpdateTime(LocalDateTime.now());
        if (!updateById(appointment)) return new BaseResDto<>(Status.ERROR);

        CompletableFuture.runAsync(() -> cleanAllCache(appointment.getUuId()));

//        if (status == MyEnum.AM_POST_REVIEW.getValue() && profile.equals("prod")) {
//            BygUndertaker taker = bygUndertakerService.getById(appointment.getUndertakerId());
//            smsUtil.noticeSms(taker.getTel(), SMSUtil.Template.TAKERNOTICE, deadMan.getName(), appointment.getUuId());
//        }
        if (status == MyEnum.AM_OPERATE.getValue()) {
            CompletableFuture.runAsync(() -> {
                // 将确定告别会的告别会UuId加入Redis的bloom过滤器
                RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter("amBF");
                bloomFilter.add(appointment.getUuId());
            });
        }
        return new BaseResDto<>(Status.SUCCESS);
    }

    @Override
    public void cleanAllCache(String amUuId) {
        // 清除多负载的逝者信息数据本地缓存
        jmsMessagingTemplate.convertAndSend(dmTopic, MyEnum.AMUUID_DM.getMessage() + amUuId);
        // 清除多负载的告别会状态数据本地缓存
        jmsMessagingTemplate.convertAndSend(amStatusTopic, MyEnum.AMUUID_STATUS.getMessage() + amUuId);
        // 清除多负载的承办人是否需要登录状态数据本地缓存
        jmsMessagingTemplate.convertAndSend(inlTopic, MyEnum.AMUUID_ISNL.getMessage() + amUuId);
    }

    @Override
    public BaseDataResDto<Boolean> isNeedLogin(String amUuId) {
        Boolean isNeedLogin;
        try {
            isNeedLogin = myCacheUtil.getFromCache(inlCMap, MyEnum.AMUUID_ISNL,
                    amUuId, MyEnum.AMBF, Boolean.class, MyEnum.ISNLLOCK, () -> {
                        BygAppointment appointment = findAppointmentByUuId(amUuId);
                        if (appointment == null) throw new ParamErrorExpectation();
                        return appointment.getStatus() == MyEnum.AM_POST_REVIEW.getValue();
                    });
        } catch (ParamErrorExpectation e) {
            e.printStackTrace();
            return new BaseDataResDto<>(Status.PARAMETERERROR);
        } catch (Exception e) {
            e.printStackTrace();
            return new BaseDataResDto<>(Status.ERROR);
        }
        return new BaseDataResDto<Boolean>(Status.SUCCESS).setData(isNeedLogin);
    }

    @Override
    public String getAmUuIdByDmId(Integer dmId) {
        LambdaQueryWrapper<BygAppointment> wrapper = Wrappers
                .lambdaQuery(BygAppointment.class).eq(BygAppointment::getDmId, dmId);
        BygAppointment appointment = getOne(wrapper);
        if (appointment == null) return null;
        return appointment.getUuId();
    }

    @Override
    public BaseResDto<Status, String> manageAm(Integer managerId, Integer amId) {
        BygAppointment appointment = getById(amId);
        Integer status = appointment.getStatus();
        if (status == MyEnum.AM_POST_REVIEW.getValue())
            return new BaseResDto<>(Status.SUCCESS);
        Integer adminId = null;
        if (Arrays.asList(0, 1).contains(status)) {
            adminId = appointment.getOperatorId();
            appointment.setOperatorId(managerId);
        }
        if (Arrays.asList(2, 3).contains(status)) {
            adminId = appointment.getReviewerId();
            appointment.setReviewerId(managerId);
        }
        if (!updateById(appointment)) return new BaseResDto<>(Status.ERROR);

        // 移除被接管告别会的操作者或审核者账号在线的用户
        if (adminId != null) {
            Integer finalAdminId = adminId;
            CompletableFuture.runAsync(() -> {
                BygAdmin admin = bygAdminService.getById(finalAdminId);
                BygRole role = bygRoleService.getById(admin.getRoleId());
                SecurityUser securityUser = new SecurityUser(admin.setRole(role));
                AdminUtil.removeUser(sessionRegistry, securityUser);
            });
        }
        return new BaseResDto<>(Status.SUCCESS);
    }

    @JmsListener(destination = "amStatusTopic")
    public void cleanAmStatusCache(TextMessage textMessage) throws JMSException {
        String finalCacheKey = textMessage.getText();
        MyCacheUtil.delInCache(amStatusCMap, finalCacheKey);
        redisTemplate.delete(finalCacheKey);
    }

    @JmsListener(destination = "inlTopic")
    public void cleanInlCache(TextMessage textMessage) throws JMSException {
        String finalCacheKey = textMessage.getText();
        MyCacheUtil.delInCache(inlCMap, finalCacheKey);
        redisTemplate.delete(finalCacheKey);
    }

    @JmsListener(destination = "amStatusLocalCacheTopic")
    public void cleanAmStatusLocalCache(TextMessage textMessage) throws JMSException {
        String finalCacheKey = textMessage.getText();
        MyCacheUtil.delInCache(amStatusCMap, finalCacheKey);
    }

    @JmsListener(destination = "inlLocalCacheTopic")
    public void cleanInlLocalCache(TextMessage textMessage) throws JMSException {
        String finalCacheKey = textMessage.getText();
        MyCacheUtil.delInCache(inlCMap, finalCacheKey);
    }

    @Override
    public BaseDataResDto<String> setPosterName(SetPosterNameReqDto data) {
        List<String> posterNames = data.getPosterNames();
        BygAppointment appointment = getById(data.getAmId());

        String[] result = checkWxCodeUrl(appointment.getWxAcodeUrl());
        if (BooleanUtils.toBoolean(result[0])) {
            appointment.setWxAcodeUrl(result[1]);
        } else if (CollectionUtils.isEmpty(posterNames)) {
            String posterUrl = appointment.getPosterUrl();
            if (StringUtils.isNotBlank(posterUrl))
                return new BaseDataResDto<String>(Status.SUCCESS).setData(posterUrl);
        }

        String posterUrl = setPosterName(appointment, posterNames);
        if (StringUtils.isBlank(posterUrl)) return new BaseDataResDto<>(Status.ERROR);

        CompletableFuture.runAsync(() -> updateById(appointment.setPosterUrl(posterUrl)));

        return new BaseDataResDto<String>(Status.SUCCESS).setData(posterUrl);
    }

    // String[0-是否改变，1-url(url_new), 2-amUuId]
    public String[] checkWxCodeUrl(String url) {
        String[] result = new String[]{"false", url, null};
        String[] url1s = url.split("/");
        if (!url1s[url1s.length-1].split("-")[0].equals(wxSecCheckUtil.getWxParam()[0])) {
            // 小程序发生切换即清除旧小程序的 access_token 并更新 wxActive 缓存
            String wxActive = RedisUtil.getObject(redisTemplate, MyEnum.WX_ACTIVE.getMessage(), String.class);
            if (!wxActive.equals(wxSecCheckUtil.getWxParam()[0])) {
                redisTemplate.delete(MyEnum.ACCESS_TOKEN_KEY.getMessage());
                redisTemplate.opsForValue().set(MyEnum.WX_ACTIVE.getMessage(), wxSecCheckUtil.getWxParam()[0]);
            }

            result[0] = "true";
            String[] url2s = url1s[url1s.length - 1].split("-");
            String amUuId = url2s[url2s.length-1].split("\\.")[0];
            result[2] = amUuId;
            url = wxSecCheckUtil.getWxAcode(amUuId, 0);
            result[1] = url;
        }
        return result;
    }

    @Override
    public BaseDataResDto<String> getAmBase64(String url) {
        try {
            String[] result = checkWxCodeUrl(url);

            if (BooleanUtils.toBoolean(result[0])) {
                LambdaUpdateWrapper<BygAppointment> updateWrapper = Wrappers.lambdaUpdate(BygAppointment.class)
                        .eq(BygAppointment::getUuId, result[2]).set(BygAppointment::getWxAcodeUrl, result[1]);
                if (!update(updateWrapper)) return new BaseDataResDto<>(Status.ERROR);
            }

            ResponseEntity<Resource> responseEntity = new RestTemplate().getForEntity(result[1], Resource.class);
            InputStream inputStream = responseEntity.getBody().getInputStream();
            byte[] encode = Base64.getEncoder().encode(IOUtils.readStreamAsByteArray(inputStream));
            String png_base64 = new String(encode, 0, encode.length, "UTF-8").trim();
            png_base64 = png_base64.replaceAll("\n", "").replaceAll("\r", "");
            return new BaseDataResDto<String>(Status.SUCCESS).setData("data:image/png;base64," + png_base64);
        } catch (IOException e) {
            e.printStackTrace();
            return new BaseDataResDto<>(Status.ERROR);
        }
    }

    public String setPosterName(BygAppointment appointment, List<String> posterNames) {
        try {
            BygDeadMan deadMan = bygDeadManService.getById(appointment.getDmId());
            if (CollectionUtils.isEmpty(posterNames)) posterNames = Arrays.asList(deadMan.getName());

            CompletableFuture<BufferedImage> headImageFuture = CompletableFuture.supplyAsync(() -> {
                try {
                    return ImageIO.read(new URL(deadMan.getHeadUrl()));
                } catch (IOException e) {
                    e.printStackTrace();
                    return null;
                }
            });

            CompletableFuture<BufferedImage> wxImageFuture = CompletableFuture.supplyAsync(() -> {
                try {
                    return ImageIO.read(new URL(appointment.getWxAcodeUrl()));
                } catch (IOException e) {
                    e.printStackTrace();
                    return null;
                }
            });

            LambdaQueryWrapper<BygBgTemplate> wrapper = Wrappers
                    .lambdaQuery(BygBgTemplate.class).eq(BygBgTemplate::getBgId, deadMan.getBgId())
                    .in(BygBgTemplate::getType, Arrays.asList(MyEnum.BG_TEMPLATE.getValue(), MyEnum.BG_TYPE_HEAD_BR.getValue()));
            List<BygBgTemplate> bgTemplates = bygBgTemplateService.list(wrapper);

            CompletableFuture<BufferedImage> bgImageFuture = CompletableFuture.supplyAsync(() -> {
                try {
                    BygBgTemplate bgTemplate = bgTemplates.parallelStream()
                            .filter(bg -> bg.getType() == MyEnum.BG_TEMPLATE.getValue()).findFirst().get();
                    return ImageIO.read(new URL(bgTemplate.getUrl()));
                } catch (IOException e) {
                    e.printStackTrace();
                    return null;
                }
            });

            CompletableFuture<BufferedImage> headBrImageFuture = CompletableFuture.supplyAsync(() -> {
                try {
                    BygBgTemplate headBrTemplate = bgTemplates.parallelStream().filter(bg -> bg.getType() == MyEnum.BG_TYPE_HEAD_BR.getValue()).findFirst().get();
                    return ImageIO.read(new URL(headBrTemplate.getUrl()));
                } catch (IOException e) {
                    e.printStackTrace();
                    return null;
                }
            });

            CompletableFuture<Color[]> colorsFuture = CompletableFuture.supplyAsync(() -> {
                BygBgTemplate bgTemplate = bgTemplates.parallelStream()
                        .filter(bg -> bg.getType() == MyEnum.BG_TEMPLATE.getValue()).findFirst().get();
                return Arrays.asList(bgTemplate.getText().split(";")).stream().map(cStr -> {
                    Integer[] rgb = Arrays.asList(cStr.split(",")).stream()
                            .map(Integer::valueOf).collect(Collectors.toList()).toArray(new Integer[3]);
                    return new Color(rgb[0], rgb[1], rgb[2]);
                }).collect(Collectors.toList()).toArray(new Color[3]);
            });

            String y2y = String.format("%s-%s", deadMan.getBirthday().getYear(), deadMan.getDeadday().getYear());
            BufferedImage posterImage = MakeOver.pressImage(bgImageFuture.join(), headImageFuture.join(),
                    headBrImageFuture.join(), wxImageFuture.join(), colorsFuture.join(), y2y, posterNames);
            if (posterImage == null) return null;

            ByteArrayOutputStream os = new ByteArrayOutputStream();
            ImageIO.write(posterImage, "png", os);
            InputStream is = new ByteArrayInputStream(os.toByteArray());
            return ossUtil.upload("poster-" + appointment.getUuId() + "-" + System.currentTimeMillis() + ".png", is);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

}
